
package cn.jeeweb.core.query.resolver;

import com.google.common.collect.Lists;

import cn.jeeweb.core.common.form.BaseMethodArgumentResolver;
import cn.jeeweb.core.query.annotation.PageableDefaults;
import cn.jeeweb.core.query.data.PageRequest;
import cn.jeeweb.core.query.data.Pageable;
import cn.jeeweb.core.query.data.Sort;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.MethodParameter;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;

/**
 * <p>
 * 请求分页数据绑定到Pageable，支持请求参数和uri template数据的绑定
 * </p>
 * <p>
 * 使用指南：
 * 
 * <pre>
 *   1.1、简单的分页请求参数格式如下：
 *     page.size=10  分页大小
 *     page.pn=1    页码 从1开始
 *   1.2、控制器处理方法写法
 *     public void test(Pageable page);
 * </pre>
 * 
 * <pre>
 *   2.1、带排序的分页请求参数格式如下：
 *     page.size=10  分页大小
 *     page.pn=1    页码 从1开始
 *     sort.a.b=desc
 *     sort.c=asc
 *   默认按照排序关键词的字典顺序排（因为Map存储是无序） 如果想有序排 可以在sort之后跟一个顺序号
 *     sort2.a.b=desc
 *     sort1.c=asc
 *   2.2、控制器处理方法写法
 *     public void test(Pageable page);
 * </pre>
 * 
 * <pre>
 *   3.1、带前缀的 排序分页请求参数格式如下：
 *     test_page.size=10  分页大小
 *     test_page.number=1    页码 从1开始
 *     test_sort.a.b=desc
 *     test_sort.c=asc
 *     foo_page.size=10  分页大小
 *     foo_page.number=1    页码 从1开始
 *     foo_sort.a.b=desc
 *     foo_sort.c=asc
 *
 *   排序默认按照请求时顺序排
 *   3.2、控制器处理方法写法
 *     public void test(@Qualifier("test") Pageable page1, @Qualifier("test") Pageable page2);
 * </pre>
 * <p/>
 * 
 * <pre>
 *     错误的用法，如果有多个请使用@Qualifier指定前缀
 *     public void fail(Pageable page1, Pageable page2);
 * </pre>
 * <p/>
 * <p>
 * 
 * @author: 王存见
 *          <p>
 * @Date: 17-02-11 下午23:40
 *        <p>
 * @version: 1.0
 */
public class PageableMethodArgumentResolver extends BaseMethodArgumentResolver {

	private static final Pageable DEFAULT_PAGE_REQUEST = new PageRequest(0, 10);
	private static final String DEFAULT_PAGE_PREFIX = "page";
	private static final String DEFAULT_SORT_PREFIX = "sort";

	private Pageable fallbackPagable = DEFAULT_PAGE_REQUEST;
	private String pagePrefix = DEFAULT_PAGE_PREFIX;
	private String sortPrefix = DEFAULT_SORT_PREFIX;

	private int minPageSize = 1;
	private int maxPageSize = 100;

	/**
	 * 设置最小分页大小 默认10
	 *
	 * @param minPageSize
	 */
	public void setMinPageSize(int minPageSize) {
		this.minPageSize = minPageSize;
	}

	/**
	 * 设置最大分页大小 默认100
	 *
	 * @param maxPageSize
	 */
	public void setMaxPageSize(int maxPageSize) {
		this.maxPageSize = maxPageSize;
	}

	/**
	 * Setter to configure a fallback instance of {@link Pageable} that is being
	 * used to back missing parameters. Defaults to
	 * {@value #DEFAULT_PAGE_REQUEST}.
	 *
	 * @param fallbackPagable
	 *            the fallbackPagable to set
	 */
	public void setFallbackPagable(Pageable fallbackPagable) {
		this.fallbackPagable = null == fallbackPagable ? DEFAULT_PAGE_REQUEST : fallbackPagable;
	}

	/**
	 * Setter to configure the prefix of request parameters to be used to
	 * retrieve paging information. Defaults to {@link #DEFAULT_PAGE_PREFIX}.
	 *
	 * @param pagePrefix
	 *            the prefix to set
	 */
	public void setPagePrefix(String pagePrefix) {
		this.pagePrefix = null == pagePrefix ? DEFAULT_PAGE_PREFIX : pagePrefix;
	}

	public void setSortPrefix(String sortPrefix) {
		this.sortPrefix = null == sortPrefix ? DEFAULT_SORT_PREFIX : sortPrefix;
	}

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return Pageable.class.isAssignableFrom(parameter.getParameterType());
	}

	@Override
	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

		PageableDefaults pageableDefaults = getPageableDefaults(parameter);
		// 默认的page request
		Pageable defaultPageRequest = getDefaultFromAnnotationOrFallback(pageableDefaults);

		String pageableNamePrefix = getPagePrefix(parameter);
		String sortNamePrefix = getSortPrefix(parameter);
		Map<String, String[]> pageableMap = getPrefixParameterMap(pageableNamePrefix, webRequest, true);
		Map<String, String[]> sortMap = getPrefixParameterMap(sortNamePrefix, webRequest, false);
		// 预处理排序,sort与order分开
		String gridtype = webRequest.getParameter("gridtype");
		if (!StringUtils.isEmpty(gridtype) && gridtype.equals("jqgrid")) {
			sortMap.clear();
			String sort = webRequest.getParameter("sort");
			String order = webRequest.getParameter("order");
			if (sort.contains("asc") || sort.contains("desc")) {
				String[] sortArr = sort.split(",");
				for (String sortItem : sortArr) {
					String[] sortItemArr = sortItem.trim().split(" ");
					if (sortItemArr.length == 2) {
						String[] orderList = { sortItemArr[1].trim() };
						sortMap.put("sort." + sortItemArr[0].trim(), orderList);
					} else {
						String[] orderList = { order };
						sortMap.put("sort." + sortItemArr[0].trim(), orderList);
					}
				}
			} else {
				String[] orderList = { webRequest.getParameter("order") };
				sortMap.put("sort." + webRequest.getParameter("sort"), orderList);
			}
		}
		if (sortMap.isEmpty()) {
			if (!StringUtils.isEmpty(webRequest.getParameter("sort"))) {
				String[] orderList = { webRequest.getParameter("order") };
				sortMap.put("sort." + webRequest.getParameter("sort"), orderList);
			}
		}
		Sort sort = getSort(sortNamePrefix, sortMap, defaultPageRequest, webRequest);
		if (pageableMap.size() == 0) {
			return new PageRequest(defaultPageRequest.getPageNumber(), defaultPageRequest.getPageSize(),
					sort == null ? defaultPageRequest.getSort() : sort);
		}

		int pn = getPageNumber(pageableMap, defaultPageRequest);
		int pageSize = getPageSize(pageableMap, defaultPageRequest);

		return new PageRequest(pn, pageSize, sort);

	}

	private Sort getSort(String sortNamePrefix, Map<String, String[]> sortMap, Pageable defaultPageRequest,
			NativeWebRequest webRequest) {
		Sort sort = null;
		try {
			List<OrderedSort> orderedSortList = Lists.newArrayList();
			for (String name : sortMap.keySet()) {
				String value = sortMap.get(name)[0];
				if (!StringUtils.isEmpty(value)) {
					// sort1.abc
					int propertyIndex = name.indexOf(".") + 1;
					int order = 0;
					String orderStr = name.substring(sortNamePrefix.length(), propertyIndex - 1);

					if (!StringUtils.isEmpty(orderStr)) {
						order = Integer.valueOf(orderStr);
					}

					String property = name.substring(propertyIndex);
					assertSortProperty(property);
					Sort.Direction direction = Sort.Direction.fromString(sortMap.get(name)[0]);
					orderedSortList.add(new OrderedSort(property, direction, order));
				}
			}
			Collections.sort(orderedSortList);
			for (OrderedSort orderedSort : orderedSortList) {
				Sort newSort = new Sort(orderedSort.direction, orderedSort.property);
				if (sort == null) {
					sort = newSort;
				} else {
					sort = sort.and(newSort);
				}
			}
		} catch (Exception e) {
		}
		if (sort == null) {
			return defaultPageRequest.getSort();
		}
		return sort;
	}

	/**
	 * 防止sql注入，排序字符串只能包含字符 数字 下划线 点 ` "
	 *
	 * @param property
	 */
	private void assertSortProperty(String property) {
		if (!property.matches("[a-zA-Z0-9_、.`\"]*")) {
			throw new IllegalStateException("Sort property error, only contains [a-zA-Z0-9_.`\"]");
		}
	}

	private int getPageSize(Map<String, String[]> pageableMap, Pageable defaultPageRequest) {
		int pageSize = 0;
		try {
			String pageSizeStr = pageableMap.get("size")[0];
			if (pageSizeStr != null) {
				pageSize = Integer.valueOf(pageSizeStr);
			} else {
				pageSize = defaultPageRequest.getPageSize();
			}
		} catch (Exception e) {
			pageSize = defaultPageRequest.getPageSize();
		}

		if (pageSize < minPageSize) {
			pageSize = minPageSize;
		}

		if (pageSize > maxPageSize) {
			pageSize = maxPageSize;
		}
		return pageSize;
	}

	private int getPageNumber(Map<String, String[]> pageableMap, Pageable defaultPageRequest) {
		int number = 1;
		try {
			String numberStr = pageableMap.get("pn")[0];
			if (numberStr != null) {
				number = Integer.valueOf(numberStr);
			} else {
				number = defaultPageRequest.getPageNumber();
			}
		} catch (Exception e) {
			number = defaultPageRequest.getPageNumber();
		}

		if (number < 1) {
			number = 1;
		}

		return number;
	}

	/**
	 * Resolves the prefix to use to bind properties from. Will prepend a
	 * possible {@link Qualifier} if available or return the configured prefix
	 * otherwise.
	 *
	 * @param parameter
	 * @return
	 */
	private String getPagePrefix(MethodParameter parameter) {

		Qualifier qualifier = parameter.getParameterAnnotation(Qualifier.class);

		if (qualifier != null) {
			return new StringBuilder(((Qualifier) qualifier).value()).append("_").append(pagePrefix).toString();
		}

		return pagePrefix;
	}

	private String getSortPrefix(MethodParameter parameter) {

		Qualifier qualifier = parameter.getParameterAnnotation(Qualifier.class);

		if (qualifier != null) {
			return new StringBuilder(qualifier.value()).append("_").append(sortPrefix).toString();
		}

		return sortPrefix;
	}

	private Pageable getDefaultFromAnnotationOrFallback(PageableDefaults pageableDefaults) {

		Pageable defaultPageable = defaultPageable(pageableDefaults);
		if (defaultPageable != null) {
			return defaultPageable;
		}

		return fallbackPagable;
	}

	private PageableDefaults getPageableDefaults(MethodParameter parameter) {
		// 首先从参数上找
		PageableDefaults pageableDefaults = parameter.getParameterAnnotation(PageableDefaults.class);
		// 找不到从方法上找
		if (pageableDefaults == null) {
			pageableDefaults = parameter.getMethodAnnotation(PageableDefaults.class);
		}
		return pageableDefaults;
	}

	private Pageable defaultPageable(PageableDefaults pageableDefaults) {

		if (pageableDefaults == null) {
			return null;
		}

		int pageNumber = pageableDefaults.pageNumber();
		int pageSize = pageableDefaults.value();

		String[] sortStrArray = pageableDefaults.sort();
		Sort sort = null;

		for (String sortStr : sortStrArray) {
			String[] sortStrPair = sortStr.split("=");
			Sort newSort = new Sort(Sort.Direction.fromString(sortStrPair[1]), sortStrPair[0]);
			if (sort == null) {
				sort = newSort;
			} else {
				sort = sort.and(newSort);
			}
		}
		return new PageRequest(pageNumber, pageSize, sort);
	}

	/**
	 * Asserts uniqueness of all {@link Pageable} parameters of the method of
	 * the given {@link MethodParameter}.
	 *
	 * @param parameter
	 */
	@SuppressWarnings("unused")
	private void assertPageableUniqueness(MethodParameter parameter) {

		Method method = parameter.getMethod();

		if (containsMoreThanOnePageableParameter(method)) {
			Annotation[][] annotations = method.getParameterAnnotations();
			assertQualifiersFor(method.getParameterTypes(), annotations);
		}
	}

	/**
	 * Returns whether the given {@link Method} has more than one
	 * {@link Pageable} parameter.
	 *
	 * @param method
	 * @return
	 */
	private boolean containsMoreThanOnePageableParameter(Method method) {

		boolean pageableFound = false;

		for (Class<?> type : method.getParameterTypes()) {

			if (pageableFound && type.equals(Pageable.class)) {
				return true;
			}

			if (type.equals(Pageable.class)) {
				pageableFound = true;
			}
		}

		return false;
	}

	/**
	 * Asserts that every {@link Pageable} parameter of the given parameters
	 * carries an {@link org.springframework.beans.factory.annotation.Qualifier}
	 * annotation to distinguish them from each other.
	 *
	 * @param parameterTypes
	 * @param annotations
	 */
	private void assertQualifiersFor(Class<?>[] parameterTypes, Annotation[][] annotations) {

		Set<String> values = new HashSet<String>();

		for (int i = 0; i < annotations.length; i++) {

			if (Pageable.class.equals(parameterTypes[i])) {

				Qualifier qualifier = findAnnotation(annotations[i]);

				if (null == qualifier) {
					throw new IllegalStateException(
							"Ambiguous Pageable arguments in handler method. If you use multiple parameters of type Pageable you need to qualify them with @Qualifier");
				}

				if (values.contains(qualifier.value())) {
					throw new IllegalStateException("Values of the user Qualifiers must be unique!");
				}

				values.add(qualifier.value());
			}
		}
	}

	/**
	 * Returns a {@link Qualifier} annotation from the given array of
	 * {@link Annotation}s. Returns {@literal null} if the array does not
	 * contain a {@link Qualifier} annotation.
	 *
	 * @param annotations
	 * @return
	 */
	private Qualifier findAnnotation(Annotation[] annotations) {

		for (Annotation annotation : annotations) {
			if (annotation instanceof Qualifier) {
				return (Qualifier) annotation;
			}
		}

		return null;
	}

	static class OrderedSort implements Comparable<OrderedSort> {
		private String property;
		private Sort.Direction direction;
		private int order = 0; // 默认0 即无序

		OrderedSort(String property, Sort.Direction direction, int order) {
			this.property = property;
			this.direction = direction;
			this.order = order;
		}

		@Override
		public int compareTo(OrderedSort o) {
			if (o == null) {
				return -1;
			}
			if (this.order > o.order) {
				return 1;
			} else if (this.order < o.order) {
				return -1;
			} else {
				return 0;
			}
		}
	}

}
